/*
 * Copyright 2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.pajamas.starter.integration.module.client;

import com.google.common.collect.Maps;

import org.aopalliance.aop.Advice;
import org.apache.commons.lang3.StringUtils;
import org.pajamas.daydream.template.sequence.SequenceTemplate;
import org.pajamas.starter.core.util.RegisterAsPajamasClient;
import org.pajamas.starter.integration.module.context.PajamasPropSrcConstants;
import org.pajamas.starter.integration.spring.aop.AspectJAdvisorBuilder;
import org.pajamas.starter.integration.spring.bean.BeanConstants;
import org.pajamas.starter.integration.spring.caching.RedissonCacheConfigBuilder;
import org.pajamas.starter.integration.spring.cloud.AutoIncreaseIDGenerator;
import org.pajamas.starter.integration.spring.cloud.ClusterSynchronizedAdvice;
import org.pajamas.starter.integration.spring.cloud.GUIDGenerator;
import org.redisson.api.RedissonClient;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonSpringCacheManager;
import org.springframework.aop.Advisor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.CompositeCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;

import java.util.Map;

/**
 * @author william
 * @since 2024/4/2
 */
@ConditionalOnClass(RedissonClient.class)
@EnableConfigurationProperties({RedisProperties.class, PajamasCacheClientProperties.class})
@RegisterAsPajamasClient.ConfigurationBean
public class PajamasCacheClientConfiguration {
    private static final String SEQUENCE_PATTERN_ASPECTJ_EXPRESSION =
            "execution (* {" + PajamasPropSrcConstants.BASE_PACKAGE_APP + "}..service.*Service.*(..)) " +
                    "&& @within(org.springframework.stereotype.Service)" +
                    "&& @annotation(org.pajamas.starter.core.service.ClusterSynchronized)";

    @ConditionalOnProperty(prefix = "org.pajamas.starter.client.cache", value = "distributedCacheManager",
            havingValue = "true", matchIfMissing = true)
    @ConditionalOnBean(RedissonClient.class)
    @ConditionalOnMissingBean(RedissonSpringCacheManager.class)
    @Bean
    public CacheManager distributedCacheManager(RedissonClient client,
                                                ObjectProvider<RedissonCacheConfigBuilder> customizers) {
        Map<String, CacheConfig> configMap = Maps.newConcurrentMap();
        customizers.forEach(builder -> configMap.put(builder.getName(), builder.build()));
        return new RedissonSpringCacheManager(client, configMap);
    }

    @ConditionalOnProperty(prefix = "org.pajamas.starter.client.cache", value = "compositeCacheManager",
            havingValue = "true", matchIfMissing = true)
    @ConditionalOnMissingBean(CompositeCacheManager.class)
    @Bean
    public CacheManager compositeCacheManager(ObjectProvider<CacheManager> cacheManagers) {
        return new CompositeCacheManager(cacheManagers.orderedStream().toArray(CacheManager[]::new));
    }

    @ConditionalOnProperty(prefix = "org.pajamas.starter.client.cache", value = "autoIncreaseIDGenerator",
            havingValue = "true", matchIfMissing = true)
    @ConditionalOnBean(RedissonClient.class)
    @ConditionalOnMissingBean
    @Bean
    public GUIDGenerator autoIncreaseIDGenerator(RedissonClient redissonClient) {
        return new AutoIncreaseIDGenerator(redissonClient);
    }

    @ConditionalOnProperty(prefix = "org.pajamas.starter.client.cache", value = "clusterSynchronizedAdvice",
            havingValue = "true", matchIfMissing = true)
    @ConditionalOnBean(RedissonClient.class)
    @Bean("pajamasClusterSynchronizedAdvice")
    public Advice pajamasClusterSynchronizedAdvice(RedissonClient redissonClient, SequenceTemplate sequenceTemplate) {
        return new ClusterSynchronizedAdvice(redissonClient, sequenceTemplate);
    }

    @ConditionalOnBean(value = Advice.class, name = "pajamasClusterSynchronizedAdvice")
    @Order(BeanConstants.ORDER_EARLIEST_SECOND)
    @Bean
    public Advisor pajamasClusterSynchronizedAdvisor(@Qualifier("pajamasClusterSynchronizedAdvice") Advice advice,
                                                     PajamasCacheClientProperties properties,
                                                     SequenceTemplate sequenceTemplate) {
        String defaultAspectJExpression = sequenceTemplate.create(SEQUENCE_PATTERN_ASPECTJ_EXPRESSION).interpretSelf();
        return AspectJAdvisorBuilder
                .newBuilder()
                .pointcut(StringUtils.firstNonBlank(properties.getAspectJExpression(), defaultAspectJExpression))
                .advice(advice)
                .build();
    }
}
